#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#include "parse.h"

/* FIXME: 
 * these lexer does not properly handle simple triad
 * ex.) 'C' should be C major triad
 */

int chord_flatsharp = 0;        /* -1 for flat, 0 for none, 1 for sharp */
int chord_category = 0;         /* C, D, E, F, G, A, B */
int chord_arg = 0;

#define STEP_NOTE       0
#define STEP_CHORD      1
#define STEP_VARIATION  2
#define STEP_FIN        3

static const char *parse_variation(const char *chord, int *token);
static const char *parse_chord(const char *chord, int *token);
static const char *parse_note(const char *chord, int *token);
static const char *eat_number(const char *s, int *n);
static const char *eat_flatsharp(const char *chord);
static const char *chord2str(int chord);
static char note2char(int note);

#define CHORD_STR_MAX   12

#ifdef TEST
int
main(int argc, char *argv[])
{
  int token;

  token = chord_tok(argv[1]);
  if (token == -1) {
    printf("error\n");
    return 1;
  }

  do {
    if (token == 0)
      break;

    switch (token) {
    case TK_NOTE:
      printf("Note: %c%s\n",
             note2char(chord_arg),
             (chord_flatsharp > 0) ? "#" : ((chord_flatsharp < 0) ? "b" : ""));
      break;
    case TK_CHORD:
      printf("Chord: %s-%d\n", chord2str(chord_category), chord_arg);
      break;

    case TK_VARIATION:
      printf("Variation: %s%d\n",
             (chord_flatsharp > 0) ? "#" : ((chord_flatsharp < 0) ? "b" : ""),
             chord_arg);
      break;
    default:
      abort();
    }
  } while ((token = chord_tok(NULL)) != -1);
  return 0;
}
#endif  /* TEST */


int
chord_tok(const char *s)
{
  static const char *chordstr = 0;

  /* step = 0(root_note) 1(chord_name) 2(chord_num) 3([variation...]) */
  static int step = 0;

  int token = TK_INVALID;

  if (s) {
    chordstr = s;
    step = STEP_NOTE;
  }

  switch (step) {
  case STEP_NOTE:
    chordstr = parse_note(chordstr, &token);
    if (token == TK_INVALID)
      return TK_INVALID;
    step = (chordstr) ? STEP_CHORD : STEP_FIN;
    break;

  case STEP_CHORD:
    chordstr = parse_chord(chordstr, &token);
    if (token == TK_INVALID)
      return TK_INVALID;
    step = (chordstr) ? STEP_VARIATION : STEP_FIN;
    break;

  case STEP_VARIATION:
    chordstr = parse_variation(chordstr, &token);
    if (token == TK_EOL)
      step = STEP_FIN;
    break;

  case STEP_FIN:
    return TK_EOL;

  default:
    abort();
  }
  return token;
}


/*
 * chord := root_note chord_name chord_num [variation...]
 *
 */

/*
   root_note = C, C#, Db, D, D#, Eb, E, E#, Fb, F, F#, Gb, G, G#,
               Ab, A, A#, Bb, B, B#, Cb

   chord_name = major = M | maj | M7 | maj7 | ..9 | .. 13 minor = m |
     min | dominant = epsilon | 7 | 9 | 11 | 13 diminished = dim,
     dim7, augmented = aug aug7 aug9, aug11, aug13 ads sadf are sdtg
     fdg fdg dgf fd dfgfd gdfg dfg df gdf d

     sus = sus sus4
     half-dim = o

   variation = b5, #9, b9, #11, #13,

 */

static const char *
eat_flatsharp(const char *chord)
{
  if (*chord == '\0')
    return NULL;

  switch (*chord) {
  case '#':
    chord_flatsharp = 1;
    chord++;
    break;
  case 'b':
    chord_flatsharp = -1;
    chord++;
    break;
  default:
    chord_flatsharp = 0;
    break;
  }
  return chord;
}


static const char *
eat_number(const char *s, int *n)
{
  if (sscanf(s, "%d", n) != 1)
    return NULL;

  while (*s) {
    if (*s < '0' || *s > '9')
      break;
    s++;
  }
  return s;
}


static const char *
parse_note(const char *chord, int *token)
{
  if (*chord >= 'A' && *chord <= 'G') {
    chord_arg = (*chord - 'A') - 2;
    if (chord_arg < 0)
      chord_arg += 7;
    *token = TK_NOTE;
  }
  else {
    *token = TK_INVALID;
    return chord;
  }

  chord++;
  chord = eat_flatsharp(chord);
  return chord;
}


static const char *
parse_chord(const char *chord, int *token)
{
  const char *p;
  char str[CHORD_STR_MAX] = { 0, };

  p = strpbrk(chord, "b#0123456789");
  if (p != NULL)
    memcpy(str, chord, p - chord);
  else
    strncpy(str, chord, CHORD_STR_MAX - 1);

  if (strcmp(str, "M") == 0 || strcmp(str, "maj") == 0)
    chord_category = CHORD_MAJ;
  else if (strcmp(str, "m") == 0 ||
           strcmp(str, "min") == 0 ||
           strcmp(str, "-") == 0)
    chord_category = CHORD_MIN;
  else if (strcmp(str, "dim") == 0)
    chord_category = CHORD_DIM;
  else if (strcmp(str, "aug") == 0 || strcmp(str, "+") == 0)
    chord_category = CHORD_AUG;
  else if (strcmp(str, "sus") == 0)
    chord_category = CHORD_SUS;
  else if (strcmp(str, "o") == 0)
    chord_category = CHORD_HDIM;
  else
    chord_category = CHORD_DOM;

  if (p) {
    if (*p >= '0' && *p <= '9')
      p = eat_number(p, &chord_arg);
    else {
      if (chord_category == CHORD_DOM)
        chord_category = CHORD_MAJ;
      chord_arg = 0;
    }
  }
  *token = TK_CHORD;
  return p;
}


static const char *
parse_variation(const char *chord, int *token)
{
  const char *p = chord;

  *token = TK_INVALID;

  if (*p == '\0') {
    *token = TK_EOL;
    return p;
  }

  p = eat_flatsharp(p);
  if (p == chord)
    return p;

  p = eat_number(p, &chord_arg);
  if (!p)
    return p;

  *token = TK_VARIATION;
  return p;
}


static const char *
chord2str(int chord)
{
  static const char *names[] = {
    "NONE",
    "MAJ",
    "MIN",
    "DOM",
    "HDIM",
    "DIM",
    "AUG",
    "SUS",
  };

  if (chord > 0 && chord <= CHORD_LAST)
    return names[chord];

  return names[0];
}


static char
note2char(int note)
{
  if (note < 0 || note > 6)
    return 'N';

  if (note < 5)
    return 'C' + note;
  return 'A' + note - 5;
}
